package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.HotArticleService;
import com.heima.common.exception.CustException;
import com.heima.feigns.AdminFeign;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.constants.article.ArticleConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.app.ArticleVisitStreamMess;
import com.heima.utils.common.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class HotArticleServiceImpl implements HotArticleService {
    @Autowired
    ApArticleMapper apArticleMapper;


    @Override
    public void computeHotArticle() {
        // 1. 查询近5天的文章
        // 1.1  得到5天前的时间
        String params = LocalDateTime.now().minusDays(5).format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
        // 1.2  调用articleMapper查询
        List<ApArticle> apArticles = apArticleMapper.selectArticleByDate(params);
        if(CollectionUtils.isEmpty(apArticles)){
            return;
        }
        // 2. 计算每篇文章的热度值  将文章封装成vo集合
        List<HotArticleVo> hotArticleVoList = computeHotArticle(apArticles);
//        System.out.println(hotArticleVoList);
        // 3. 按照频道 每个频道下缓存热度最高的30条文章
        cacheTagToRedis(hotArticleVoList);
    }

    @Override
    public void updateApArticle(ArticleVisitStreamMess mess) {
        // 1. 根据 文章id查询出文章数据
        ApArticle article = apArticleMapper.selectById(mess.getArticleId());
        if (article==null) {
            log.error("未查询到相关文章信息 文章id:{}",mess.getArticleId());
            return;
        }
        // 2. 更新文章 各个行为的值
        int newComment = (int)(article.getComment()==null?0:article.getComment() + mess.getComment());
        article.setComment(newComment<0 ? 0:newComment);

        int newLike = (int)(article.getLikes() == null ? 0 : article.getLikes() + mess.getLike());
        article.setLikes(newLike < 0 ? 0 : newLike);

        int newView = (int) (article.getViews() == null ? 0 : article.getViews() + mess.getView());
        article.setViews(newView<0 ? 0 : newView);

        int newCollect = (int) (article.getCollection() == null ? 0 : article.getCollection() + mess.getCollect());
        article.setCollection(newCollect<0 ? 0 : newCollect);

        apArticleMapper.updateById(article);
        // 3.  计算文章得分
        Integer score = computeScore(article);
        // 4.  判断文章是否是今日发布  如果是整体热度*3
        String nowStr = DateUtils.dateToString(new Date()); // 今日
        String publishStr = DateUtils.dateToString(article.getPublishTime());// 发布日期
        if(nowStr.equals(publishStr)){
            score = score * 3;
        }
        // 5. 查询对应频道热点文章，替换分值较低文章
        updateArticleCache(article,score,ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId());
        // 6. 查询推荐频道热点文章，替换分值较低文章
        updateArticleCache(article,score,ArticleConstants.HOT_ARTICLE_FIRST_PAGE+ ArticleConstants.DEFAULT_TAG);
    }

    /**
     * @param article 当前文章数据
     * @param score  该文章最新得分
     * @param cacheKey  要更新缓存的key
     */
    private void updateArticleCache(ApArticle article, Integer score, String cacheKey) {
        // 1. 获取redis中热点文章的缓存数据
        ValueOperations<String, String> valueOper = redisTemplate.opsForValue();
        String articleVoListJson = valueOper.get(cacheKey);
        if(StringUtils.isNotBlank(articleVoListJson)){
            boolean isHas = false;
            // 2. 判断当前文章是否已经存在于热点文章中
            List<HotArticleVo> hotArticleVoList = JSON.parseArray(articleVoListJson, HotArticleVo.class);
            //          如果存在直接更新热度值
            for (HotArticleVo articleVo : hotArticleVoList) {
                if(articleVo.getId().equals(article.getId())){
                    articleVo.setScore(score); // 重新设置得分
                    isHas = true;
                    break;
                }
            }
            // 3. 如果不存在  直接将文章封装成vo对象，加入到热点文章集合中
            if(!isHas){
                HotArticleVo articleVo = new HotArticleVo();
                BeanUtils.copyProperties(article,articleVo);
                articleVo.setScore(score);
                hotArticleVoList.add(articleVo);
            }
            // 4. 重新对热点文章进行排序 并截取前30条热点文章
            hotArticleVoList = hotArticleVoList.stream()
                            .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                            .limit(30)
                            .collect(Collectors.toList());
            // 5. 缓存到redis中
            valueOper.set(cacheKey,JSON.toJSONString(hotArticleVoList));
        }
    }


    @Autowired
    AdminFeign adminFeign;

    /**
     * 按照频道  缓存  文章
     * 按热度降序排序   每个频道 只缓存热度最高的30条文章
     * 推荐频道  缓存所有文章  热度最高的30条文章
     * @param hotArticleVoList
     */
    private void cacheTagToRedis(List<HotArticleVo> hotArticleVoList) {
        // 1. 远程查询频道列表
        ResponseResult<List<AdChannel>> channlResult = adminFeign.selectChannels();
        if(!channlResult.checkCode()){
            log.error("远程查询频道失败");
            CustException.cust(AppHttpCodeEnum.REMOTE_SERVER_ERROR,"远程查询频道信息失败");
        }
        List<AdChannel> channelList = channlResult.getData();
        // 2. 遍历频道，  在所有文章中挑出该频道的文章  调用保存方法 保存热点文章
        channelList.forEach(channel -> {
            List<HotArticleVo> articleByChannel = hotArticleVoList.stream()
                    .filter(articleVo -> channel.getId().equals(articleVo.getChannelId()))
                    .collect(Collectors.toList());
            sortAndCache(articleByChannel,ArticleConstants.HOT_ARTICLE_FIRST_PAGE+channel.getId());
        });
        // 3. 缓存推荐频道    调用保存放 保存热点文章
        sortAndCache(hotArticleVoList,ArticleConstants.HOT_ARTICLE_FIRST_PAGE+ArticleConstants.DEFAULT_TAG);
    }
    @Autowired
    StringRedisTemplate redisTemplate;
    /**
     * 将热点文章 排序 截取前30条  缓存到指定的redis key中
     * @param articleVoList
     * @param cacheKey
     */
    private void sortAndCache(List<HotArticleVo> articleVoList, String cacheKey) {
        // 按照文章热度降序 排序 截取前30条文章
        List<HotArticleVo> hotArticleVoList = articleVoList.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());
        // 将30条文章 缓存到redis
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(hotArticleVoList));
    }

    private List<HotArticleVo> computeHotArticle(List<ApArticle> apArticles) {
        // 1. 循环边路文章列表
        return apArticles.stream().map(article->{
            //     计算每篇文章的得分  按照权重去计算
            HotArticleVo articleVo = new HotArticleVo();
            BeanUtils.copyProperties(article,articleVo);
            Integer score = computeScore(article);
            articleVo.setScore(score);
            //     将article封装成vo对象
            return articleVo;
        }).collect(Collectors.toList());
    }

    /**
     * 计算文章热度得分
     * @param article
     * @return
     */
    private Integer computeScore(ApArticle article) {
        // 按照权重计算文章得分
        Integer score = 0;
        if(article.getViews()!=null){
            score += article.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        if(article.getLikes()!=null){
            score += article.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if(article.getCollection()!=null){
            score += article.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        if(article.getComment()!=null){
            score += article.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        return score;
    }
}
